#!/usr/bin/env node
"use strict";

const { spawn } = require("child_process");
const fs = require("fs");
const path = require("path");

function fileExists(p) {
  try {
    fs.accessSync(p, fs.constants.F_OK);
    return true;
  } catch {
    return false;
  }
}

function toPosixPath(p) {
  return p.replace(/\\/g, "/");
}

function escapeFilterPath(p) {
  // 转为 POSIX 路径并转义冒号，避免 Windows 盘符 D: 触发 ffmpeg 过滤器参数分隔
  const posix = toPosixPath(p);
  return posix.replace(/:/g, "\\:");
}

function hexToAssBgr(hex) {
  // hex: #RRGGBB or RRGGBB
  const h = hex.replace(/^#/, "");
  if (h.length !== 6) return "&H00FFFFFF";
  const rr = h.slice(0, 2);
  const gg = h.slice(2, 4);
  const bb = h.slice(4, 6);
  return `&H00${bb}${gg}${rr}`; // &HAABBGGRR (AA=00 opaque)
}

// 预设样式
const STYLE_PRESETS = {
  clean: (fontName = "Arial") => ({
    FontName: fontName,
    FontSize: 12,
    PrimaryColour: hexToAssBgr("#FFFFFF"),
    OutlineColour: hexToAssBgr("#000000"),
    BorderStyle: 1,
    Outline: 1,
    Shadow: 0,
    Bold: 0,
    MarginV: 40,
  }),
  contrast: (fontName = "Arial") => ({
    FontName: fontName,
    FontSize: 12,
    PrimaryColour: hexToAssBgr("#FFD36E"), // 温暖黄
    OutlineColour: hexToAssBgr("#000000"),
    BorderStyle: 1,
    Outline: 1,
    Shadow: 0,
    Bold: 1,
    MarginV: 40,
  }),
  shadow: (fontName = "Arial") => ({
    FontName: fontName,
    FontSize: 14,
    PrimaryColour: hexToAssBgr("#FFFFFF"),
    OutlineColour: hexToAssBgr("#000000"),
    BorderStyle: 1,
    Outline: 0,
    Shadow: 2,
    Bold: 0,
    MarginV: 40,
  }),
};

// 位置到 ASS Alignment 的映射（水平始终居中）
const POSITION_TO_ALIGNMENT = {
  bottom: 2, // 底部居中
  middle: 5, // 垂直居中
  top: 8, // 顶部居中
};

function buildForceStyle(styleObj, position) {
  const alignment = POSITION_TO_ALIGNMENT[position] || POSITION_TO_ALIGNMENT.bottom;
  const merged = { ...styleObj, Alignment: alignment };
  return Object.entries(merged)
    .map(([k, v]) => `${k}=${v}`)
    .join(",");
}

function escapeFilterValue(v) {
  // 转义 ffmpeg 滤镜参数中的特殊字符
  return String(v)
    .replace(/\\/g, "\\\\")
    .replace(/:/g, "\\:")
    .replace(/,/g, "\\,");
}

function escapeSingleQuotes(v) {
  return String(v).replace(/'/g, "\\'");
}

function runFfmpeg(args) {
  return new Promise((resolve, reject) => {
    const child = spawn("ffmpeg", args, { stdio: "inherit", shell: false });
    child.on("error", reject);
    child.on("close", (code) => {
      if (code === 0) resolve();
      else reject(new Error(`ffmpeg exit code ${code}`));
    });
  });
}

async function main() {
  const argv = process.argv;
  const iIdx = argv.indexOf("-i");
  const sIdx = argv.indexOf("-s") > -1 ? argv.indexOf("-s") : argv.indexOf("--srt");
  const oIdx = argv.indexOf("-o");
  const styleIdx = argv.indexOf("--style");
  const posIdx = argv.indexOf("--position");
  const fontIdx = argv.indexOf("--font");
  const crfIdx = argv.indexOf("--crf");
  const presetIdx = argv.indexOf("--preset");
  const fontSizeIdx = argv.indexOf("--fontSize");
  const marginVIdx = argv.indexOf("--marginV");

  const inputVideo = iIdx > -1 ? argv[iIdx + 1] : "assets/lihun.mp4";
  const inputSubs = sIdx > -1 ? argv[sIdx + 1] : "assets/lihun.srt";
  const outputVideo = oIdx > -1 ? argv[oIdx + 1] : path.join(path.dirname(inputVideo) || ".", `${path.parse(inputVideo).name}_subbed.mp4`);
  const styleName = styleIdx > -1 ? argv[styleIdx + 1] : "contrast";
  const position = posIdx > -1 ? argv[posIdx + 1] : "bottom";
  const fontName = fontIdx > -1 ? argv[fontIdx + 1] : undefined;
  const crf = crfIdx > -1 ? argv[crfIdx + 1] : "20";
  const preset = presetIdx > -1 ? argv[presetIdx + 1] : "veryfast";
  const overrideFontSize = fontSizeIdx > -1 ? Number(argv[fontSizeIdx + 1]) : null;
  const overrideMarginV = marginVIdx > -1 ? Number(argv[marginVIdx + 1]) : null;

  if (!fileExists(inputVideo)) {
    console.error(`❌ 输入视频不存在: ${inputVideo}`);
    process.exit(1);
  }
  if (!fileExists(inputSubs)) {
    console.error(`❌ 字幕文件不存在: ${inputSubs}`);
    process.exit(1);
  }

  const absVideo = path.resolve(inputVideo);
  const absSubs = path.resolve(inputSubs);
  const absOutput = path.resolve(outputVideo);

  const presetBuilder = STYLE_PRESETS[styleName];
  if (!presetBuilder) {
    console.error(`❌ 未知样式: ${styleName}，可选: ${Object.keys(STYLE_PRESETS).join(", ")}`);
    process.exit(1);
  }
  const styleObj = presetBuilder(fontName);
  if (Number.isFinite(overrideFontSize) && overrideFontSize > 0) {
    styleObj.FontSize = overrideFontSize;
  }
  if (Number.isFinite(overrideMarginV) && overrideMarginV >= 0) {
    styleObj.MarginV = overrideMarginV;
  }
  const forceStyle = buildForceStyle(styleObj, position);

  // 使用 forward slash 避免 Windows 路径转义问题
  // 使用单引号包裹参数，并对盘符冒号进行转义（D\:）
  const subsPathForFilter = escapeFilterPath(absSubs);
  const subtitlesFilter = `subtitles=filename='${escapeSingleQuotes(subsPathForFilter)}':force_style='${escapeSingleQuotes(forceStyle)}'`;

  const args = [
    "-y",
    "-i", absVideo,
    "-vf", subtitlesFilter,
    "-c:v", "libx264",
    "-preset", preset,
    "-crf", String(crf),
    "-c:a", "copy",
    absOutput,
  ];

  console.log(`▶️  ffmpeg ${args.join(" ")}`);

  try {
    await runFfmpeg(args);
    console.log(`✅ 已生成: ${absOutput}`);
  } catch (e) {
    console.error(`❌ ffmpeg 失败: ${e.message}`);
    process.exit(2);
  }
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});


